﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Diagnostics;
using System.Windows.Threading;
using System.Data.SqlTypes;
using System.IO;
using System.Drawing;
using System.Threading;
using System.Windows.Media.Imaging;

namespace Player
{
    public abstract class SessionBase : INotifyPropertyChanged
    {
        private PlayingThread m_play;
        private DownloadThread m_download;
        private ExportWmvThread m_exportWmv;
        private PreviewThread m_preview;
        private SearchThread m_search;

        private bool m_adjustBufferStart;
        private const double RECOVER_CONNECTION_TIME_INTERVAL = 30;
        private long m_bufferEndTimeStamp;
        private DispatcherTimer m_recoverConnectionTimer;
        private bool m_fastForwarding;

        protected FrameBuffer m_frames;
        public RenderEngineBase Engine;

        public string UserName { get; protected set; }
        public string MachineName { get; protected set; }
        public DateTime StartTime { get; protected set; }
        public DateTime UtcStartTime { get; protected set; }
        public DateTime EndTime { get; protected set; }
        public Guid SessionId { get; protected set; }
        public abstract RenderEngine CreateRenderEngine();
        public abstract IEnumerable<Frame> LoadFrames(long timeStamp);
        public abstract IEnumerable<Frame> SearchFrames(long timeStamp, string[] searchWords);
        public abstract IEnumerable<EventItem> LoadEvents();
        public abstract long Start { get; }
        public abstract long End { get; }

        //public double Length { get { return new TimeSpan(End - Start).TotalMilliseconds; } } //TODO: Why do we need Milliseconds here?  Why can't we use Ticks?
        public double Length { get { return (End - Start); } } //TODO: Why do we need Milliseconds here?  Why can't we use Ticks?

        #region Status Properties

        private bool m_gotoInProgress;
        public bool GotoInProgress
        {
            get { return m_gotoInProgress; }
            set
            {
                if (m_gotoInProgress != value)
                {
                    m_gotoInProgress = value;
                    OnPropertyChanged("GotoInProgress");
                }
            }
        }

        private bool m_isPlaying;
        public bool IsPlaying
        {
            get { return m_isPlaying; }
            set
            {
                if (m_isPlaying != value)
                {
                    m_isPlaying = value;
                    OnPropertyChanged("IsPlaying");
                }
            }
        }

        private bool m_exportingWmv;
        public bool ExportingWMV
        {
            get { return m_exportingWmv; }
            set
            {
                if (m_exportingWmv != value)
                {
                    m_exportingWmv = value;
                    OnPropertyChanged("ExportingWMV");
                }
            }
        }

        private bool m_searching;
        public bool Searching
        {
            get { return m_searching; }
            set
            {
                if (m_searching != value)
                {
                    m_searching = value;
                    OnPropertyChanged("Searching");
                }
            }
        }
        #endregion

        public event EventHandler<BitmapImageArgs> ImageChange;

        public BitmapSource _image;
        public BitmapSource Image
        {
            get { return this._image; }
            set
            {
                this._image = value;
                ImageChange(this, new BitmapImageArgs() { Image = value });
            }
        }
        
        public bool RealTimeReplay
        {
            get { return m_play.RealTimeMode; }
            set { m_play.RealTimeMode = value; }
        }

        public double ReplaySpeed
        {
            get { return m_play.Speed; }
            set { m_play.Speed = value; }
        }

        public SessionBase()
        {
            // Get the frame buffer size.  Default is 2000.  It should be okay because Unix should always have a snapshot every 2000 frames
            m_frames = new FrameBuffer(ConsoleSettings.FrameBufferSize);
            m_play = new PlayingThread(m_frames);
            m_preview = new PreviewThread(m_frames);
            m_download = new DownloadThread(this, m_frames);
            m_exportWmv = new ExportWmvThread(this);
            m_search = new SearchThread(this);

            m_frames.Updated += this.FramesBuffer_Updated;
            m_play.Updated += Session_PlayingProgressUpdated;
            m_play.Updated += OnPlayingProgressUpdated;
            //m_play.Updated += PlayingThreadProgressUpdated;
            m_play.Exit += new EventHandler<CompletedEventArgs>(m_play_Exit);
            m_preview.Updated += OnPreviewProgressUpdated;
            m_exportWmv.Updated += OnExportProgressUpdated;
            m_exportWmv.Exit += OnExportCompleted;
            m_search.Updated += OnSearchUpdated;
            m_search.Exit += this.OnSearchCompleted;
            m_download.Exit += Download_Exit;

            m_recoverConnectionTimer = new DispatcherTimer(TimeSpan.FromSeconds(RECOVER_CONNECTION_TIME_INTERVAL), DispatcherPriority.Background, RecoverConnectionTimer_Tick, Dispatcher.CurrentDispatcher);
        }

        void m_play_Exit(object sender, CompletedEventArgs e)
        {
            //throw new NotImplementedException();
        }

        void FramesBuffer_Updated(object sender, FrameBufferUpdatedEventArgs e)
        {
            if (e.EndFrame != null) m_bufferEndTimeStamp = e.EndFrame.TimeStamp;
        }

        void Download_Exit(object sender, CompletedEventArgs e)
        {
            //Because we cannot know what is the root reason of it cannot continue to download frames, so here just only check the error whether is SqlException then try to recover download frames
            if (e.Error != null && e.Error is System.Data.SqlClient.SqlException)
            {
                if (!m_recoverConnectionTimer.IsEnabled)
                {
                    Log.Verbose("It will retry download frames after {0} interval", RECOVER_CONNECTION_TIME_INTERVAL);
                    m_recoverConnectionTimer.Start();
                }
            }
        }

        void RecoverConnectionTimer_Tick(object sender, EventArgs e)
        {
            //stop recover connection timer when it try to download frames again
            if (m_recoverConnectionTimer.IsEnabled) m_recoverConnectionTimer.Stop();
            Log.Verbose("Restart to download frames");
            //because the last frame is downloaded, so the new start point need large then the last
            RestartDownloadFrames(m_bufferEndTimeStamp + 1, false);
        }

        void PlayingThreadProgressUpdated(object sender, PlayingProgressUpdatedEventArgs e)
        {
            FrameBase frame = e.Frame;
            if (!e.FastForward)
            {
                if (IsPlaying || m_adjustBufferStart)
                {
                    // If we are replaying the sesison, we should try to free up some frame 
                    // buffer here if we are using too much
                    m_frames.AdjustBufferStart(frame);
                    m_adjustBufferStart = false;
                }

                if (frame.TimeStamp >= End)
                {
                    Stop();
                }
            }
        }

        void Session_PlayingProgressUpdated(object sender, PlayingProgressUpdatedEventArgs e)
        {
            FrameBase frame = e.Frame;
            bool fastForward = e.FastForward;

            if (fastForward)
            {
                if (!m_fastForwarding)
                {
                    m_fastForwarding = true;
                    Engine.SuspendScreenUpdate();
                }
            }
            else
            {
                if (m_fastForwarding)
                {
                    m_fastForwarding = false;
                    Engine.ResumeScreenUpdate();
                }
            }
            Engine.Process(frame);
        }

        public TimeSpan GetElapsedTime(long timeStamp)
        {
            return new TimeSpan(timeStamp - Start);
        }

        public long GetTimeStamp(double normalizedTime)
        {
            return (long)((double)(End - Start) * normalizedTime + (double)Start);
        }

        public void Pause()
        {
            // Stop the render thread only
            //set sign to true, accepte the value, and join the thread to complete the task.
            //but the down load thread ??
            m_play.Stop();
            IsPlaying = false;
            Log.Verbose("Session paused");
        }

        public void Stop()
        {
            // Stop both download thread and render thread
            //Stop write, and let down first
            m_download.Stop();
            m_play.Stop();
            IsPlaying = false;
            Log.Verbose("Session stopped");
        }

        public void Play(double normalizedTime)
        {
            Log.Verbose("Normalized time: {0:F4}", normalizedTime);
            long timestamp = GetTimeStamp(normalizedTime);
            IsPlaying = true;
            Goto(timestamp);
        }

        public void Goto(long timestamp)
        {
            lock (this)
            {
                Log.Verbose("Timestamp: {0}", timestamp);

                // Let caller know we are working on a seek at the moment,
                //  if they call us again, it's just going to backup.  This 
                //  gives them a chance to tell the user "hold yer horses".
                GotoInProgress = true;

                try
                {
                    m_play.Stop();
                    m_preview.Stop();

                    if (!m_frames.Seek(timestamp))
                    {
                        // We cannot find the required frame from our in-memory buffer.
                        // Restart the download thread at that location
                        RestartDownloadFrames(timestamp, true);
                    }

                    m_play.Start(Dispatcher.CurrentDispatcher, timestamp, IsPlaying);
                }
                finally
                {
                    GotoInProgress = false;
                }
            }
        }

        public void RestartDownloadFrames(long timestamp, bool resetBuffer)
        {
            m_download.Stop();

            if (resetBuffer)
            {
                // All threads are stopped.  We are safe to reset the locks
                m_frames.Reset();
            }

            m_download.Start(Dispatcher.CurrentDispatcher, timestamp);
            m_adjustBufferStart = true;
        }

        public void Goto(double normalizedTime)
        {
            long timestamp = GetTimeStamp(normalizedTime);
            Goto(timestamp);
        }

        public void Search(long timestamp, string[] searchWords, bool forward)
        {
            Log.Verbose("Timestamp: {0}", timestamp);
            Log.Verbose("Forward: {0}", forward);
            Log.Verbose("Search words:");
            foreach (string word in searchWords)
            {
                Log.Verbose(word);
            }
            m_search.Stop();
            //Reset search begin timestamp when search begin time stamp larger than session end.
            //Since current logic timestamp always is not larger than End, here just add a safety
            if (timestamp > End) timestamp = Start;

            Searching = true;
            m_search.Start(Dispatcher.CurrentDispatcher, timestamp, searchWords, forward);
        }

        public void StopSearch()
        {
            m_search.Stop();
            Log.Verbose("Search stopped");
        }

        public void Preview(double normalizedTime)
        {
            Log.Verbose("Normalized time: {0:F4}", normalizedTime);
            long timeStamp = GetTimeStamp(normalizedTime);
            m_preview.Stop();
            m_preview.Start(Dispatcher.CurrentDispatcher, this, timeStamp);
        }

        public bool ExportWmv(string filename)
        {
            //Log.Verbose("Filename: {0}", filename);
            //// Make sure we can first...
            //if (!WmvFile.TestWmvCoreExists())
            //{
            //    //new MessageBox(MessageBoxType.Error, "Unable to initialize WindowMedia system, you must have WindowsMedia Player installed.").ShowModal();
            //    return false;
            //}

            //m_exportWmv.Stop();
            //ExportingWMV = true;
            //m_exportWmv.Start(Dispatcher.CurrentDispatcher, filename);
            return true;
        }

        public void StopExportWmv()
        {
            m_exportWmv.Stop();
            Log.Verbose("Export WMV stopped");
        }

        #region Debugging Aides

        /// <summary>
        /// Warns the developer if this object does not have
        /// a public property with the specified name. This 
        /// method does not exist in a Release build.
        /// </summary>
        [Conditional("DEBUG")]
        [DebuggerStepThrough]
        public void VerifyPropertyName(string propertyName)
        {
            // Verify that the property name matches a real,  
            // public, instance property on this object.
            if (TypeDescriptor.GetProperties(this)[propertyName] == null)
            {
                string msg = "Invalid property name: " + propertyName;

                if (this.ThrowOnInvalidPropertyName)
                    throw new ArgumentException(msg);
                else
                    Debug.Fail(msg);
            }
        }

        /// <summary>
        /// Returns whether an exception is thrown, or if a Debug.Fail() is used
        /// when an invalid property name is passed to the VerifyPropertyName method.
        /// The default value is false, but subclasses used by unit tests might 
        /// override this property's getter to return true.
        /// </summary>
        protected virtual bool ThrowOnInvalidPropertyName { get; set; }

        #endregion // Debugging Aides

        #region INotifyPropertyChanged Members

        /// <summary>
        /// Raised when a property on this object has a new value.
        /// View model class should report events synchronously
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// Raises this object's PropertyChanged event.
        /// </summary>
        /// <param name="propertyName">The property that has a new value.</param>
        protected virtual void OnPropertyChanged(string propertyName)
        {
            this.VerifyPropertyName(propertyName);

            var e = new PropertyChangedEventArgs(propertyName);
            //m_propertyChanged.Invoke(this, e);
        }

        #endregion // INotifyPropertyChanged Members

        #region Public Events

        public event EventHandler<CompletedEventArgs> PlayingCompleted;

        public event EventHandler<PlayingProgressUpdatedEventArgs> PlayingProgressUpdated;
        protected void OnPlayingProgressUpdated(object sender, PlayingProgressUpdatedEventArgs e)
        {
            if (PlayingProgressUpdated != null)
            {
                PlayingProgressUpdated(sender, e);
            }
        }

        public event EventHandler<FrameBufferUpdatedEventArgs> FrameBufferUpdated;
        protected void OnFrameBufferUpdated(Object sender, FrameBufferUpdatedEventArgs e)
        {
            if (FrameBufferUpdated != null)
            {
                FrameBufferUpdated(sender, e);
            }
        }

        public event EventHandler<PreviewProgressUpdatedEventArgs> PreviewProgressUpdated;
        protected void OnPreviewProgressUpdated(object sender, PreviewProgressUpdatedEventArgs e)
        {
            if (PreviewProgressUpdated != null)
            {
                PreviewProgressUpdated(sender, e);
            }
        }

        public event EventHandler<ExportProgressUpdatedEventArgs> ExportProgressUpdated;
        protected void OnExportProgressUpdated(Object sender, ExportProgressUpdatedEventArgs e)
        {
            if (ExportProgressUpdated != null)
            {
                ExportProgressUpdated(sender, e);
            }
        }

        public event EventHandler<CompletedEventArgs> ExportCompleted;
        protected void OnExportCompleted(object sender, CompletedEventArgs e)
        {
            if (ExportCompleted != null)
            {
                ExportCompleted(sender, e);
                ExportingWMV = false;
            }
        }

        public event EventHandler<SearchingProgressUpdatedEventArgs> SearchUpdated;
        protected void OnSearchUpdated(object sender, SearchingProgressUpdatedEventArgs e)
        {
            if (SearchUpdated != null)
            {
                SearchUpdated(sender, e);
            }
        }

        public event EventHandler<CompletedEventArgs> SearchCompleted;
        protected void OnSearchCompleted(object sender, CompletedEventArgs e)
        {
            if (SearchCompleted != null)
            {
                SearchCompleted(sender, e);
                Searching = false;
            }
        }
        #endregion
    }

    public class SessionStatusUpdatedEventArgs : EventArgs
    {
        public SessionStatusUpdatedEventArgs(bool playing, bool exporting, bool searching)
        {
            Playing = playing;
            Exporting = exporting;
            Searching = searching;
        }

        public bool Playing { get; protected set; }
        public bool Exporting { get; protected set; }
        public bool Searching { get; protected set; }
    }

    //public class EventItemConverter
    //{
    //    private struct ApplicationId
    //    {
    //        private int m_processId;
    //        private long m_windowId;

    //        public int ProcessId
    //        {
    //            set
    //            {
    //                m_processId = value;
    //            }
    //        }

    //        public long WindowId
    //        {
    //            set
    //            {
    //                m_windowId = value;
    //            }
    //        }
    //    }

    //    // The map stores the application names.  Some events doesn't have the application name filled.
    //    // We fill in the application name according to process ID and windows ID.
    //    private Dictionary<ApplicationId, string> m_applicationMap = new Dictionary<ApplicationId, string>();
    //    public Windows.EventItem Convert(WindowsSessionGetEvents.Output input)
    //    {
    //        DateTime time = new DateTime(input.SystemTime);
    //        Windows.EventItem ret = new Windows.EventItem()
    //        {
    //            DateTime = time.ToLocalTime(),
    //            TimeStamp = input.TimeStamp,
    //            Application = input.Module,
    //            Type = input.Event,
    //            Pid = input.Pid,
    //            Text = input.Text,
    //            WindowId = input.WindowId,
    //            X = input.X,
    //            Y = input.Y,
    //            W = input.Width,
    //            H = input.Height
    //        };

    //        Log.Trace("DateTime: {0}", ret.DateTime);
    //        Log.Trace("TimeStamp: {0}", ret.TimeStamp);
    //        Log.Trace("Application: {0}", ret.Application);
    //        Log.Trace("Type: {0}", ret.Type);
    //        Log.Trace("Pid: {0}", ret.Pid);
    //        Log.Trace("Text: {0}", ret.Text);
    //        Log.Trace("WindowId: {0}", ret.WindowId);
    //        Log.Trace("X: {0}", ret.X);
    //        Log.Trace("Y: {0}", ret.Y);
    //        Log.Trace("W: {0}", ret.W);
    //        Log.Trace("H: {0}", ret.H);


    //        if (input.Event == WashEventType.Activate || input.Event == WashEventType.Settext || input.Event == WashEventType.WindowActivate)
    //        {
    //            // Module is actually a pipe delimited string:
    //            // Application.exe|ProductName|FileDescription|Company|Version
    //            // For now we only want to show the File Description if set, falling back on exe name                
    //            string module = input.Module;
    //            string description = null;
    //            int barIndex = module.IndexOf('|');
    //            if (barIndex > 0)
    //            {
    //                string[] sections = module.Split(new char[] { '|' }, StringSplitOptions.None); // include empty entries                    
    //                if (sections.Length >= 3 && !string.IsNullOrEmpty(sections[2]))
    //                {
    //                    description = sections[2];   // Use the file description
    //                }

    //                if (string.IsNullOrEmpty(description) && sections.Length > 0)
    //                {
    //                    module = sections[0];   // Use the app name                        
    //                    if (module.EndsWith(".exe", StringComparison.OrdinalIgnoreCase))
    //                        module = module.Substring(0, module.Length - 4);
    //                }
    //                else
    //                {
    //                    module = description;
    //                }
    //            }

    //            if (string.IsNullOrEmpty(module) && !string.IsNullOrEmpty(input.Text))
    //                module = input.Text;
    //            ret.Application = module != null ? module.Trim() : module;
    //            Log.Trace("Application updated: {0}", ret.Application);
    //        }

    //        if (!String.IsNullOrEmpty(ret.Application))
    //        {
    //            m_applicationMap[new ApplicationId() { ProcessId = input.Pid, WindowId = input.WindowId }] = ret.Application;
    //        }
    //        else
    //        {
    //            ApplicationId key = new ApplicationId() { ProcessId = input.Pid, WindowId = input.WindowId };
    //            if (m_applicationMap.ContainsKey(key)) ret.Application = m_applicationMap[key];
    //            else ret.Application = "<Unknown>";
    //            Log.Trace("Application updated: {0}", ret.Application);
    //        }

    //        return ret;
    //    }

    //    public Unix.EventItem Convert(UnixSessionGetEvents.Output input)
    //    {
    //        Log.Trace("DateTime: {0}", input.Time);
    //        Log.Trace("TimeStamp: {0}", input.TimeStamp);
    //        Log.Trace("Command: {0}", input.CommandString);
    //        Log.Trace("Sequence: {0}", input.Sequence);

    //        Unix.EventItem ret = new Unix.EventItem
    //        {
    //            DateTime = input.Time.ToLocalTime(),
    //            TimeStamp = input.TimeStamp,
    //            Command = input.CommandString,
    //            Sequence = input.Sequence
    //        };

    //        return ret;
    //    }
    //}

    public static class DatabaseHelper
    {
        public static List<int> AuditStoreDatabaseIds = new List<int>();
        static DatabaseHelper()
        {
            AuditStoreDatabaseIds.Add(1);
        }
        public static int GetStartDatabaseIdForWindowsSession(long timestamp)
        {
            return 0;
        }
    }

    public class ExportCdfThread : WorkerThread<ExportProgressUpdatedEventArgs>
    {
        public ExportCdfThread(SessionBase session)
        {
        }

        protected override void Run(params object[] args)
        {
            throw new NotImplementedException();
        }
    }
}
